﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.SessionState;
using System.Web.Routing;
using System.Reflection;
using SimpleRest.Core.Routing;
using SimpleRest.Core.Routing;

namespace SimpleRest.Core.Extensions.Routes
{
    public class XsdHttpHandler: IHttpHandler
    {
        public readonly Configuration Configuration;
        public readonly RequestContext RequestContext;
        private readonly Type ReturnType;
        public Pipeline OperationPipeline { get; private set; }

        public XsdHttpHandler(Pipeline operationPipeline, Type returnType, Configuration configuration, RequestContext requestContext)
        {
            this.ReturnType = returnType;
            this.OperationPipeline = operationPipeline;
            this.Configuration = configuration;
            this.RequestContext = requestContext;
        }

        public bool IsReusable
        {
            get { return false; }
        }

        public void ProcessRequest(HttpContext context)
        {
            var request = new HttpRequestMessage(this.RequestContext);
            var response = new HttpResponseMessage() { StatusCode = System.Net.HttpStatusCode.OK };
            var operation = new Operation("Get.XSD", typeof(string), context.Request.Url.AbsoluteUri, null, "GET", new List<OperationParameter>());

            var responseHandlers = this.OperationPipeline.ExecuteMessageHandlers(new MessageHandlerContext(request, OperationPipeline.ServiceType, operation), this.Configuration);
            bool isSuccessful = true;
            IMediaTypeFormatter responseMediaTypeFormatter = null;

            try
            {
                Type t = ReturnType;
                if (t.GetInterface(typeof(IOperationResponse).FullName) != null)
                {
                    var genericParams = t.GetGenericArguments();
                    if (genericParams.Count() == 0)
                        return;
                    else
                        t = genericParams.First();
                }
                SimpleRest.Serialization.XsdSerializer ser = new Serialization.XsdSerializer();
                ser.IsOutputFormatted = this.Configuration is ExtendedConfiguration && (this.Configuration as ExtendedConfiguration).ExtendedOptions.IsXsdFormatted;
                var xsd = ser.Serialize(t);
                context.Response.Write(xsd);
            }
            catch (Exception ex)
            {
                isSuccessful = false;
            }
            finally
            {
                this.OperationPipeline.ExecuteResponseHandlers(responseHandlers, response);
            }

            if (!isSuccessful)
            {
                context.Response.Clear();

                responseMediaTypeFormatter = Pipeline.GetResponseMediaTypeFormatter(new HttpRequestMessage(this.RequestContext), this.Configuration);

                if (responseMediaTypeFormatter == null || !responseMediaTypeFormatter.CanSerialize(this.ReturnType))
                {
                    context.Response.StatusCode = (int)System.Net.HttpStatusCode.UnsupportedMediaType; ;
                    context.Response.End();
                    return;
                }


                var stream = responseMediaTypeFormatter.Serialize(response.Content);
                string responseText = System.Text.Encoding.UTF8.GetString(ReadToEnd(stream));

                context.Response.Write(responseText);
                context.Response.End();
            }
        }

        /// <summary>
        /// Helper method to turn a stream into a byte array.  Pulled this thing off of stack overflow and it seems to work well.
        /// </summary>
        /// <param name="stream"></param>
        /// <returns></returns>
        private static byte[] ReadToEnd(System.IO.Stream stream)
        {
            long originalPosition = 0;

            if (stream.CanSeek)
            {
                originalPosition = stream.Position;
                stream.Position = 0;
            }

            try
            {
                byte[] readBuffer = new byte[stream.Length];

                int totalBytesRead = 0;
                int bytesRead;

                while ((bytesRead = stream.Read(readBuffer, totalBytesRead, readBuffer.Length - totalBytesRead)) > 0)
                {
                    totalBytesRead += bytesRead;

                    if (totalBytesRead == readBuffer.Length)
                    {
                        int nextByte = stream.ReadByte();
                        if (nextByte != -1)
                        {
                            byte[] temp = new byte[readBuffer.Length * 2];
                            Buffer.BlockCopy(readBuffer, 0, temp, 0, readBuffer.Length);
                            Buffer.SetByte(temp, totalBytesRead, (byte)nextByte);
                            readBuffer = temp;
                            totalBytesRead++;
                        }
                    }
                }

                byte[] buffer = readBuffer;
                if (readBuffer.Length != totalBytesRead)
                {
                    buffer = new byte[totalBytesRead];
                    Buffer.BlockCopy(readBuffer, 0, buffer, 0, totalBytesRead);
                }
                return buffer;
            }
            finally
            {
                if (stream.CanSeek)
                {
                    stream.Position = originalPosition;
                }
            }
        }
    }
}
